#!/usr/bin/perl

# This is omnimysql, a program that allows one to issue queries simultaneously 
# to many MySQL hosts.
# http://code.google.com/p/omnimysql
#
# This program is copyright (c) 2007 Ryan Lowe.
# Feedback and improvements are welcome (ryan.a.lowe@gmail.com)
#
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
# licenses.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA  02111-1307  USA.

use strict;
use warnings FATAL => 'all';
use Pod::Usage;
use Getopt::Long qw ( :config no_ignore_case bundling );
use English qw ( -no_match_vars );
use XML::Simple;
use DBI;
use Term::ReadLine;
use DBIx::MyParsePP;

sub do_query;

my $PROGRAM = 'omnimysql';
my $VERSION = '0.0.4';
my $WEBSITE = 'http://code.google.com/p/omnimysql';

################################################################################
# Get configuration information
################################################################################

my %options;

my $gop = new Getopt::Long::Parser;

if ( !$gop->getoptions (
  'config=s'       => \$options { 'config' },
  'help|?'         => \$options { 'help' },
  'print-defaults' => \$options { 'defaults' },
  'version|V'      => \$options { 'version' } ) ) {

  pod2usage ( 2 );

}

################################################################################
# Die when necessary, then set sane defaults
################################################################################

pod2usage ( 2 ) if ( $#ARGV eq -1 );

pod2usage ( 2 ) if ( $options { 'help' } );

if ( $options { 'version' } ) {

  print $PROGRAM . ' ' . $VERSION . 
        ' (Check for updates at: ' . $WEBSITE . ")\n";
  exit;

}

$options { 'config' } = '/etc/omnimysql.cnf' if ( !$options { 'config' } );

$options { 'group' } = $ARGV [ $#ARGV ];

if ( $options { 'defaults' } ) {

  print "$PROGRAM defaults are:

ATTRIBUTE                  VALUE
-------------------------- ------------------
config                     /etc/omnimysql.cnf
help                       FALSE
print-defaults             FALSE
version                    FALSE
";

  exit;

}

################################################################################
# Begin the main program
################################################################################

my $parser           = DBIx::MyParsePP->new() or die 
                       "Could not create new DBIx::MyParsePP";

my $version         = '0.0.4';
my $prompt          = 'omnimysql> ';
my $prompt_continue = '         > ';

# READ CONFIG FILE
my $xml = XML::Simple->new() or die "Could not create a new XML::Simple";
my $config;
eval { $config = $xml->XMLin ( $options { 'config' } ); };

if ($@) {

  print 'Config file (' . $options { 'config' } . 
        ") does not exist ... exiting\n";
  exit(2);

}

# Make sure the specified group is valid
if ( !exists $config->{ 'group' }{ $options { 'group' } } ) { 

  die ( 'Invalid Group: ' . $options { 'group' } . "\n" ); 

}
 
my %hash_config = %{ $config->{ 'group' }{ $options { 'group' } } };

my $user     = $hash_config{ 'user' };
my $password = $hash_config{ 'password' } ? $hash_config{ 'password' } : '';
my $ssl      = $hash_config{ 'ssl' } if ($hash_config{ 'ssl' });

my $sql = '';

my $i = 0;

# No ornamental display for Term::ReadLine
$ENV { PERL_RL } = 'Perl o=0';

my $term = new Term::ReadLine 'omnimysql';

# HISTORY
my $history_file = $ENV{ HOME } . '/.omnimysql_history';

# Create if necessary
open ( CREATE_HISTORY, '>>' . $history_file );
close CREATE_HISTORY;

# Now read it ... because +>> does not work GRRRR
open ( READ_HISTORY, $history_file );
my @history = <READ_HISTORY>;
close READ_HISTORY;

open ( HISTORY, '>>' . $history_file );

for ( my $e = 0; $e < scalar ( @history ); $e++ ) { 
 
  chomp ( $history[ $e ] );
  $term->addhistory ( $history[ $e ] );

}

while ( defined ( $_ = $term->readline ( $i eq 0 ? $prompt : $prompt_continue ) ) ) {

  if  ( chomp ( $_ ) ne '' ) {

    chomp ( $sql .= ' ' . $_ );

    my $query = $parser->parse( $sql );

    if ( $sql eq ' exit' || $sql eq ' exit;' ) { 

      close HISTORY;
      exit;

    } elsif ( substr ( $sql, length ( $sql ) - 2 ) eq '\c' ) {

      $sql = '';
      $i = 0;

    } elsif ( defined $query->getRoot() && $sql =~ /;+$/ ) {

      print HISTORY "$_\n";
      &do_query ( $query->toString() );
      $sql = '';
      $i = 0;

    } else {

      print HISTORY "$_\n";
      $i = 1;

    }

  }

}

sub do_query() {

  my $query = $_[0];
  foreach my $host (keys %{ $hash_config{ 'hosts' }->{ 'host' } }) {

    my $connection_string = "DBI:mysql:database=" . 
           $hash_config{ 'hosts' }->{ 'host' }{ $host }->{ 'dbname' } . 
           ";host=$host;port=";

    $connection_string   .= $hash_config{ 'hosts' }{ $host }{ 'port' } ? 
                            $hash_config{ 'hosts' }{ $host }{ 'port' } : '3306';

    my $p = $password;

    if ($hash_config{ 'hosts' }{ $host }{ 'password' }) {

      $p = $hash_config{ 'hosts' }{ $host }{ 'password' };

    }

    my $u = $user;
    
    if ($hash_config{ 'hosts' }{ $host }{ 'user' }) {

      $u = $hash_config{ 'hosts' }{ $host }{ 'user' };

    }

    my $dbh = eval { 
 
      DBI->connect ( $connection_string, $u, $p, { RaiseError => 1, PrintError => 0 } ); 

    };

    if ($@) {

      print "$host: Could Not Connect\n";
      next;

    }

    my $sth = $dbh->prepare($query);

    eval { $sth->execute() };

    if ($@) {

      print "$host: " . $sth->errstr . "\n";
      next;

    }

    my $i = 1;

    print "$host:\n";
    while (my $href = $sth->fetchrow_hashref) {

      print "\t***** $i. row *****\n";

      foreach my $key (keys %{$href}) {

        $key = ( $key ? $key : '' );
        $href->{$key} = ( $href->{$key} ? $href->{$key} : '' );
        print "\t", $key, ': ', $href->{$key}, "\n";

      }

      $i++;

    }

  $sth->finish();

  # Disconnect
  $dbh->disconnect();

  }

}

=pod

=head1 NAME

omnimysql - Allows one to issue queries simultaneously to many MySQL hosts

=head1 SYNOPSIS

 omnimysql [options] group

 Options:
   --config=<full_path_to_file> The config file to use
   --help                       Display this help and exit.
   --print-defaults             Print the program argument list and exit.
   --version                    Output version information and exit.
   group                        The group (as defined in the config file)
                                against which to issue the queries

 defaults are:

 ATTRIBUTE                  VALUE
 -------------------------- ------------------
 config                     /etc/omnimysql.cnf
 help                       FALSE
 print-defaults             FALSE
 version                    FALSE

=head1 DESCRIPTION

omnimysql is a tool that allows one to issue queries simultaneously to
many MySQL instances

=head1 SYSTEM REQUIREMENTS

omnimysql 0.0.2, requires the following Perl modules:

  Pod::Usage
  SQL::Parser
  XML::Simple
  DBI
  DBD::mysql

=head1 BUGS

Please report all bugs and feature requests to L<$WEBSITE>.

=head1 LICENSE

This program is copyright (c) 2007 Ryan Lowe.
Feedback and improvements are welcome (ryan.a.lowe@gmail.com).

THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
systems, you can issue `man perlgpl' or `man perlartistic' to read these
licenses.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA  02111-1307  USA.

=head1 AUTHOR

Ryan Lowe (ryan.a.lowe@gmail.com)

=head1 VERSION

This manual page documents 0.0.4 of omnimysql.

=cut

